#include <stdexcept>

#include "gl_triangle_mesh.h"

#include "obj_loader.h"

using namespace eqClient;
using namespace eqClient::obj_loader;

namespace
{
    class VAOHolder : private GLEWContextHolder
    {
        public:
            VAOHolder(GLuint array, GLEWContext const* context):
            GLEWContextHolder(context)
            { glBindVertexArray(array); }
            
            ~VAOHolder()
            { glBindVertexArray(0); }
    };
}

template <typename T>
static inline std::size_t vectorSize(std::vector<T> const& vec)
{
    return vec.size() * sizeof(T);
}

GLTriangleMesh::GLTriangleMesh(const obj_loader::ObjectFile& objFile, const GLEWContext* context):
GLEWContextHolder(context),
vertexBuffer(0),
vertexArray(0),
vertexCount(0)
{
    std::vector<Vertex>   const& vertexList   = objFile.getVertexList();
    std::vector<TexCoord> const& texCoordList = objFile.getTexCoordList();
    std::vector<Normal>   const& normalList   = objFile.getNormalList();
    std::vector<Face>     const& faceList     = objFile.getFaceList();
    
    std::vector<Vertex>   vertexBufferData;
    std::vector<TexCoord> texCoordBufferData;
    std::vector<Normal>   normalBufferData;
    
    bool texCoordInUse = false, normalInUse = false;
    
    if(faceList.empty())
        throw std::runtime_error("Empty mesh not supported.");
    
    for(unsigned i = 0; i < faceList.size(); ++i)
    {
        Face const& face = faceList[i];
        
        if(face.vertexAttrIds.size() != 3)
            throw std::runtime_error("Non-triangle face is not supported in GLTriangleMesh.");
        
        for(int j = 0; j < 3; ++j)
        {
            VertexAttrId const& vertexAttrId = face.vertexAttrIds[j];
            
            int vertexId = vertexAttrId.vertexId;
            if(vertexId < 0)
                throw std::runtime_error("Non-positive vertex detected.");
            
            vertexBufferData.push_back(vertexList[vertexId]);
            
            int texCoordId = vertexAttrId.texCoordId;
            if(texCoordId >= 0)
            {
                texCoordInUse = true;
                texCoordBufferData.push_back(texCoordList[texCoordId]);
            }
            else
                texCoordBufferData.push_back(TexCoord(0, 0, 0));
            
            int normalId = vertexAttrId.normalId;
            if(normalId > 0)
            {
                normalInUse = true;
                normalBufferData.push_back(normalList[normalId]);
            }
            else
                normalBufferData.push_back(Normal(0, 0, 0));
        }
    }
    
    vertexCount = faceList.size() * 3;
    glGenVertexArrays(1, &vertexArray);
    {
        VAOHolder vaoHolder(vertexArray, glewGetContext());
        
        glGenBuffers(1, &vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        
        std::size_t bufferSize = vectorSize(vertexBufferData);
        if(texCoordInUse)
            bufferSize += vectorSize(texCoordBufferData);
        if(normalInUse)
            bufferSize += vectorSize(normalBufferData);
        
        glBufferData(GL_ARRAY_BUFFER, bufferSize, NULL, GL_STATIC_DRAW);
        
        std::ptrdiff_t bufferOffset = 0;
        
        glEnableClientState(GL_VERTEX_ARRAY);
        glBufferSubData(GL_ARRAY_BUFFER, bufferOffset, vectorSize(vertexBufferData), &vertexBufferData[0]);
        glVertexPointer(4, GL_FLOAT, 0, (GLvoid*)bufferOffset);
        bufferOffset += vectorSize(vertexBufferData);
        
        if(texCoordInUse)
        {
            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
            glBufferSubData(GL_ARRAY_BUFFER, bufferOffset, vectorSize(texCoordBufferData), &texCoordBufferData[0]);
            glTexCoordPointer(3, GL_FLOAT, 0, (GLvoid*)bufferOffset);
            bufferOffset += vectorSize(texCoordBufferData);
        }
        
        if(normalInUse)
        {
            glEnableClientState(GL_NORMAL_ARRAY);
            glBufferSubData(GL_ARRAY_BUFFER, bufferOffset, vectorSize(normalBufferData), &normalBufferData[0]);
            glNormalPointer(GL_FLOAT, 0, (GLvoid*)bufferOffset);
            bufferOffset += vectorSize(normalBufferData);
        }
    }
}

GLTriangleMesh::~GLTriangleMesh()
{
    if(vertexArray == 0)
        glDeleteVertexArrays(1, &vertexArray);
    if(vertexBuffer != 0)
        glDeleteBuffers(1, &vertexBuffer);
}

void GLTriangleMesh::draw(double begin, double end) const
{
    VAOHolder vaoHolder(vertexArray, glewGetContext());
    int idx_begin = vertexCount * begin, idx_end = vertexCount * end;
    glDrawArrays(GL_TRIANGLES, idx_begin, idx_end);
}
